﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using Microsoft.Windows.Controls.Logic;
using Microsoft.Windows.Controls.Specialized.Primitives;
using Microsoft.Windows.Controls.Specialized.TextModel;
using Microsoft.Windows.Controls.Specialized.TextExtensions;
using Microsoft.Windows.Controls.Internals;

using TextReader = System.IO.TextReader;
using System.Windows.Browser;

namespace Microsoft.Windows.Controls {

    public class CodeEditor: Control {
        private struct LineInfo {
            public TextPoint Point;
            public TextSpan Span;
            public TextBlock Visual;

            public double Top {
                get {
                    return Canvas.GetTop(Visual);
                }
                set {
                    Canvas.SetTop(Visual, value);
                }
            }

            public double Left {
                get {
                    return Canvas.GetLeft(Visual);
                }
                set {
                    Canvas.SetLeft(Visual, value);
                }
            }

            public double Width {
                get {
                    return Visual.ActualWidth;
                }
            }

            public double Height {
                get {
                    return Visual.ActualHeight;
                }
            }

            public static LineInfo Empty = new LineInfo();
        }

        private ITextBuffer _textBuffer;
        private bool _ownedBuffer;
        private Canvas _textLayer;
        private Canvas _selectionLayer;
        private Line _caret;
        private Path _selectionPath;
        private ScrollBar _horizontalScrollBar;
        private ScrollBar _veriticalScrollBar;
        private bool _mouseCaptured;
        private int _temporarySelectionStart;
        private IEditorOperations _operations;
        private ICommandMapper _mapper;
        private TextStyle _defaultStyle;

        private TextPoint _startOfFirstVisibleLine, _startOfLastVisibleLine, _startOfLastUsableLine, _caretPosition;
        private int _pageSize;
        private TextSpan _selectionSpan;
        private double _firstVisibleLineOffset = 0.0;
        private TextLineVisualFactory _lineVisualFactory;
        private IClassifier _classifier;
        private ObservableDictionary<string, TextStyle> _formatMap = new ObservableDictionary<string, TextStyle>();
        private List<LineInfo> _lineVisuals = new List<LineInfo>();
        private Point _lastPos;
        private bool _inUndo;
        private Stack<UndoItem> _undoStack = new Stack<UndoItem>();
        private Stack<UndoItem> _redoStack = new Stack<UndoItem>();
        private Stack<Stack<UndoItem>> _groupStack = new Stack<Stack<UndoItem>>();
        private List<MacroItem> _currentMacro = new List<MacroItem>();
        private bool _recording;
        private bool _inCommand;
        private TextBox _inputTextBox;

        public CodeEditor() {
            DefaultStyleKey = typeof(CodeEditor);

            _textBuffer = new LinkBuffer();

            _startOfFirstVisibleLine = new TextPoint(_textBuffer, 0, TrackingMode.Negative);
            _caretPosition = new TextPoint(_textBuffer, 0);

            _ownedBuffer = true;

            IsTabStop = true;
            TabNavigation = KeyboardNavigationMode.Cycle;

            _formatMap.Changed += delegate {
                InvalidateView();
            };

            _operations = new EditorOperations(this);
            _mapper = new CommandMapper();

            // _inputTextBox = new TextBox() { Width = 0, IsTabStop = true, TabNavigation = KeyboardNavigationMode.Cycle, AcceptsReturn = true };
            
            KeyDown += DoKeyDown;
            GotFocus += DoGotFocus;

            if (_inputTextBox != null) {
                _inputTextBox.KeyDown += DoKeyDown;
                _inputTextBox.TextChanged += InputBoxChanged;
            }
            MouseWheelHook.Hook();
        }

        public int FirstVisibleLine { 
            get { 
                if (_startOfFirstVisibleLine != null)
                    return _textBuffer.GetLineContaining(_startOfFirstVisibleLine);
                return 0;
            } 
        }

        public int LastVisibleLine { 
            get {
                if (_startOfLastVisibleLine != null)
                    return _textBuffer.GetLineContaining(_startOfLastVisibleLine);
                return 0;
            } 
        }

        public int CaretPosition {
            get {
                return _caretPosition;
            }
            set {
                var oldPos = _caretPosition;
                if (value < 0)
                    value = 0;
                else if (value > _textBuffer.Length)
                    value = _textBuffer.Length;
                _caretPosition = new TextPoint(_textBuffer, value);
                UpdateCaret();
                if (!_inUndo)
                    RecordUndo(new UndoMove(this, oldPos));
            }
        }

        public TextStyle DefaultStyle {
            get {
                return _defaultStyle;
            }
            set {
                _lineVisualFactory = null;
                _defaultStyle = value;
            }
        }

        public double CaretDistance {
            get {
                var caretPosition = CaretPosition;
                var lineInfo = GetLineInfoForPosition(caretPosition, true);
                return CharacterPosition(lineInfo, caretPosition);   
            }
        }

        public int PageSize {
            get {
                return _pageSize;
            }
        }

        public event EventHandler ViewChanged;

        public int PositionOf(int line, double distance) {
            if (line < 0 || line >= _textBuffer.LineCount)
                return 0;
            var position = _textBuffer.GetStartOfLine(line);
            var lineInfo = GetLineInfoForPosition(position, true);
            return CharacterOffsetOf(lineInfo, distance);
        }

        public void EnsureCaretVisible() {
            var caretPosition = CaretPosition;
            if (caretPosition < _startOfFirstVisibleLine.Position)
                DisplayLine(_textBuffer.GetLineContaining(caretPosition), _firstVisibleLineOffset);
            else if (caretPosition >= _startOfLastUsableLine.Position && 
                _startOfLastUsableLine.Position != _startOfLastVisibleLine.Position) {
                var line = _textBuffer.GetLineContaining(caretPosition);
                var topLine = line - PageSize;
                if (topLine < 0)
                    topLine = 0;
                DisplayLine(topLine, _firstVisibleLineOffset);
            }
        }

        /// <summary>
        /// Gets or sets the current selection span.
        /// </summary>
        public TextSpan SelectionSpan {
            get {
                return _selectionSpan;
            }

            set {
                if (value == null || value.Length == 0)
                    _selectionSpan = null;
                else
                    _selectionSpan = value;
                UpdateSelection();
            }
        }

        public IClassifier Classifier {
            get {
                return _classifier;
            }
            set {
                if (_classifier != null)
                    _classifier.ClassificationChanged -= OnClassificationChanged;
                _classifier = value;
                if (_classifier != null)
                    _classifier.ClassificationChanged += OnClassificationChanged;
                InvalidateView();
            }
        }

        public IDictionary<string, TextStyle> FormatMap {
            get {
                return _formatMap;
            }
        }

        public ITextBuffer TextBuffer {
            get {
                return _textBuffer;
            }
            set {
                if (value == null)
                    throw new ArgumentNullException("value");
                DeinitializeTextBuffer();
                _textBuffer = value;
                _ownedBuffer = false;
                InitializeTextBuffer();
                InvalidateView();
            }
        }

        public string Text {
            get {
                return _textBuffer.GetText(0, _textBuffer.Length);
            }
            set {
                if (!_ownedBuffer)
                    throw new InvalidOperationException("Cannot load into a buffer the CodeEditor doesn't own");
                var newTextBuffer = new LinkBuffer();
                newTextBuffer.Insert(0, value);
                TextBuffer = newTextBuffer;
                _ownedBuffer = true;
            }
        }

        public void DisplayLine(int lineNumber) {
            DisplayLine(lineNumber, _firstVisibleLineOffset);
        }

        public void DisplayLine(int lineNumber, double offset) {
            _startOfFirstVisibleLine = new TextPoint(_textBuffer, _textBuffer.GetStartOfLine(lineNumber), TrackingMode.Negative);
            _firstVisibleLineOffset = offset;
            double height;
            if (_textLayer != null)
                height = _textLayer.ActualHeight;
            else
                height = this.ActualHeight;

            PerformLayout(height, false);
        }

        public void Load(TextReader reader) {
            if (!_ownedBuffer)
                throw new InvalidOperationException("Cannot load into a buffer the CodeEditor doesn't own");
            Text = reader.ReadToEnd();
        }

        
        public override void OnApplyTemplate() {
            if (_textLayer != null) {
                _textLayer.MouseLeftButtonDown -= DoMouseDown;
                _textLayer.MouseMove -= DoMouseMove;
                _textLayer.MouseLeftButtonUp -= DoMouseUp;
            }
            if (_selectionLayer != null) {
                _selectionLayer.MouseLeftButtonDown -= DoMouseDown;
                _selectionLayer.MouseMove -= DoMouseMove;
                _selectionLayer.MouseLeftButtonUp -= DoMouseUp;
            }
            if (_veriticalScrollBar != null) {
                _veriticalScrollBar.Scroll -= DoVerticalScrollBarScroll;
                LayoutUpdated -= OnLayoutChanged;
            }
            if (_inputTextBox != null) {
                var parentPanel = _inputTextBox.Parent as Panel;
                if (parentPanel != null)
                    parentPanel.Children.Remove(_inputTextBox);
            }

            base.OnApplyTemplate();

            _textLayer = GetTemplateChild("TextLayer") as Canvas;
            _selectionLayer = GetTemplateChild("SelectionLayer") as Canvas;

            if (_textLayer != null) {
                _textLayer.MouseLeftButtonDown += DoMouseDown;
                _textLayer.MouseMove += DoMouseMove;
                _textLayer.MouseLeftButtonUp += DoMouseUp;
            }
            if (_selectionLayer != null) {
                _selectionLayer.MouseLeftButtonDown += DoMouseDown;
                _selectionLayer.MouseMove += DoMouseMove;
                _selectionLayer.MouseLeftButtonUp += DoMouseUp;
            }

            _selectionPath = GetTemplateChild("SelectionPath") as Path;
            _caret = GetTemplateChild("Caret") as Line;

            if (_textLayer != null)
                Cursor = Cursors.IBeam;
            if (_selectionLayer != null)
                Cursor = Cursors.IBeam;

            _veriticalScrollBar = GetTemplateChild("VerticalScrollBar") as ScrollBar;
            if (_veriticalScrollBar != null) {
                _veriticalScrollBar.Scroll += DoVerticalScrollBarScroll;
                _veriticalScrollBar.Cursor = Cursors.Arrow;
                _veriticalScrollBar.Maximum = _textBuffer.LineCount - 1;
                LayoutUpdated += OnLayoutChanged;
            }
            UpdateScrollBars();

            if (_inputTextBox != null) {
                var inputPanel = FindInputPanel();
                if (inputPanel != null) {
                    Dispatcher.BeginInvoke(delegate {
                        inputPanel.Children.Add(_inputTextBox);
                    });
                }
            }
           
        }

        private Panel FindInputPanel() {
            return FindInputPanel(this);
        }
        private Panel FindInputPanel(FrameworkElement element) {
            var parentPanel = element.Parent as Panel;
            if (parentPanel != null)
                return parentPanel;
            var parentContentControl = element.Parent as FrameworkElement;
            if (parentContentControl != null)
                return FindInputPanel(parentContentControl);
            return null;
        }

        protected override Size ArrangeOverride(Size arrangeBounds) {
            PerformLayout(arrangeBounds.Height, true);
            var result = base.ArrangeOverride(arrangeBounds);

            if (_textLayer != null)
                _textLayer.Clip = new RectangleGeometry() { Rect = new Rect(0, 0, _textLayer.ActualWidth, _textLayer.ActualHeight) };
            if (_selectionLayer != null)
                _selectionLayer.Clip = new RectangleGeometry() { Rect = new Rect(0, 0, _selectionLayer.ActualWidth, _selectionLayer.ActualHeight) };
            return result;
        }


        protected void DoMouseDown(object sender, MouseButtonEventArgs e) {
            if (_inputTextBox != null) {
                Focus();
                Dispatcher.BeginInvoke(delegate {
                    if (FocusManager.GetFocusedElement() == this)
                        _inputTextBox.Focus();
                });
            }
            else {
                IsTabStop = true;
                Focus();
            }
            if (_recording)
                MacroRecordToggle();
            Point p = e.GetPosition(this);
            foreach (var info in _lineVisuals)
                if (p.Y >= info.Top && p.Y < info.Top + info.Height) {
                    _temporarySelectionStart = CaretPosition = CharacterOffsetOf(info, p.X);
                    SelectionSpan = null;
                    _mouseCaptured = true;
                    _textLayer.CaptureMouse();
                }
        }

        protected void DoMouseMove(object sender, MouseEventArgs e) {
            if (_mouseCaptured) {
                Point p = e.GetPosition(this);
                _lastPos = p;
                foreach (var info in _lineVisuals)
                    if (p.Y >= info.Top && p.Y < info.Top + info.Height) {
                        CaretPosition = CharacterOffsetOf(info, p.X);
                        var start = _temporarySelectionStart;
                        var end = CaretPosition;
                        if (start > end) {
                            start = end;
                            end = _temporarySelectionStart;
                        }
                        SelectionSpan = new TextSpan(_textBuffer, start, end - start);
                        break;
                    }
            }
        }

        protected void DoMouseUp(object sender, MouseButtonEventArgs e) {
            if (_mouseCaptured) {
                _mouseCaptured = false;
                _textLayer.ReleaseMouseCapture();
            }
        }

        protected void DoMouseWheel(double delta) {
            if (delta > 0 && delta < 1)
                delta = 1;
            else if (delta < 0 && delta > -1)
                delta = -1;
            var newLine = FirstVisibleLine - (int)delta;
            if (newLine >= 0 && newLine < _textBuffer.LineCount)
                DisplayLine(newLine);
        }

        protected void DoVerticalScrollBarScroll(object sender, ScrollEventArgs eargs) {
            if (_veriticalScrollBar != null) {
                var newLine = _veriticalScrollBar.Value;
                if (newLine < _textBuffer.LineCount)
                    DisplayLine((int)_veriticalScrollBar.Value);
            }
        }

        protected void DoKeyDown(object sender, KeyEventArgs args) {
            _expectChange = false;
            EditCommand command = _mapper.KeyPress(args);
            if (command != EditCommand.None &&
                (sender != _inputTextBox || (command != EditCommand.Copy && command != EditCommand.Cut && command != EditCommand.Paste))) {
                PerformCommand(command);
                args.Handled = true;
            }
            else if (sender != _inputTextBox) {
                char typedChar = KeyEventChar(args);
                if (typedChar != (char)0) {
                    InsertText(typedChar.ToString());
                    args.Handled = true;
                }
            }
            else
                _expectChange = true;
        }

        private bool _expectChange;
        private void InputBoxChanged(object sender, System.Windows.Controls.TextChangedEventArgs e) {
            if (_expectChange) {
                _expectChange = false;
                InsertText(_inputTextBox.Text);
                UpdateInputBoxToSelection();
            }
        }

        private void DoGotFocus(object sender, RoutedEventArgs e) {
            if (_inputTextBox != null)
                _inputTextBox.Focus();
        }

        private void UpdateInputBoxToSelection() {
            if (_inputTextBox != null) {
                var newText = "";
                if (_selectionSpan != null && _selectionSpan.Length > 0)
                    newText = _textBuffer.GetText(_selectionSpan.Start, _selectionSpan.Length);
                if (newText != _inputTextBox.Text) {
                    _inputTextBox.Text = newText;
                    _inputTextBox.Select(0, int.MaxValue);
                }
            }
        }


        protected void DoTimerTick(object state) {
            if (_mouseCaptured) {
                Dispatcher.BeginInvoke(delegate {
                    if (_lastPos.Y < 0) {
                        var newLine = _startOfFirstVisibleLine.Line - 1;
                        if (newLine >= 0)
                            DisplayLine(newLine);
                    }
                    else if (_lastPos.Y > ActualHeight) {
                        var newLine = _startOfFirstVisibleLine + 1;
                        if (newLine + PageSize < _textBuffer.LineCount)
                            DisplayLine(newLine);
                    }
                });
            }
        }

        private static char KeyEventChar(KeyEventArgs e) {
            UpdateKeyState(e);
            char result = (char)0;
            if ((Keyboard.Modifiers & ~ModifierKeys.Shift) != 0)
                return result;
            switch (e.Key) {
                case Key.A: case Key.B: case Key.C: case Key.D: case Key.E:
                case Key.F: case Key.G: case Key.H: case Key.I: case Key.J:
                case Key.K: case Key.L: case Key.M: case Key.N: case Key.O:
                case Key.P: case Key.Q: case Key.R: case Key.S: case Key.T:
                case Key.U: case Key.V: case Key.W: case Key.X: case Key.Y:
                case Key.Z:
                    if (((Keyboard.Modifiers & ModifierKeys.Shift) != 0) != capsLock)
                        result = (char)((int)'A' + (int)(e.Key - Key.A));
                    else
                        result = (char)((int)'a' + (int)(e.Key - Key.A));
                    break;
                case Key.D0: case Key.D1: case Key.D2: case Key.D3: case Key.D4:
                case Key.D5: case Key.D6: case Key.D7: case Key.D8: case Key.D9:
                    if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0)
                        result = ")!@#$%^&*("[e.Key - Key.D0];
                    else
                        result = (char)((int)'0' + (int)(e.Key - Key.D0));
                    break;
                case Key.NumPad0: case Key.NumPad1: case Key.NumPad2: case Key.NumPad3:
                case Key.NumPad4: case Key.NumPad5: case Key.NumPad6: case Key.NumPad7:
                case Key.NumPad8: case Key.NumPad9:
                    result = (char)((int)'0' + (int)(e.Key - Key.NumPad0));
                    break;
                case Key.Add: result = '+'; break;
                case Key.Space: result = ' '; break;
                case Key.Unknown:
                    if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0)
                        switch (e.PlatformKeyCode) {
                            case 186: result = ':'; break;
                            case 187: result = '+'; break;
                            case 188: result = '<'; break;
                            case 189: result = '_'; break;
                            case 190: result = '>'; break;
                            case 191: result = '?'; break;
                            case 192: result = '~'; break;
                            case 219: result = '{'; break;
                            case 220: result = '|'; break;
                            case 221: result = '}'; break;
                            case 222: result = '"'; break;
                        }
                    else
                        switch (e.PlatformKeyCode) {
                            case 186: result = ';'; break;
                            case 187: result = '='; break;
                            case 188: result = ','; break;
                            case 189: result = '-'; break;
                            case 190: result = '.'; break;
                            case 191: result = '/'; break;
                            case 192: result = '`'; break;
                            case 219: result = '['; break;
                            case 220: result = '\\'; break;
                            case 221: result = '}'; break;
                            case 222: result = '\''; break;
                        }
                    break;
            }
            return result;
        }

        static bool capsLock;

        public static void UpdateKeyState(KeyEventArgs e) {
            switch (e.Key) {
                case Key.CapsLock: capsLock = !capsLock; break;
            }
        }

        private void PerformCommand(EditCommand command) {
            var old = _inCommand;
            var macroIgnore = false;
            try {
                _inCommand = true;
                bool select = (command & EditCommand.ExtendSelect) != 0;
                switch ((EditCommand)(command & ~EditCommand.ExtendSelect)) {
                    case EditCommand.NextCharacter: _operations.MoveToNextCharacter(select); break;
                    case EditCommand.PreviousCharacter: _operations.MoveToPreviousCharacter(select); break;
                    case EditCommand.NextWord: _operations.MoveToNextWord(select); break;
                    case EditCommand.PreviousWord: _operations.MoveToPreviousWord(select); break;
                    case EditCommand.MoveLineUp: _operations.MoveLineUp(select); break;
                    case EditCommand.MoveLineDown: _operations.MoveLineDown(select); break;
                    case EditCommand.PageUp: _operations.PageUp(select); break;
                    case EditCommand.PageDown: _operations.PageDown(select); break;
                    case EditCommand.MoveToStartOfLine: _operations.MoveToStartOfLine(select); break;
                    case EditCommand.MoveToEndOfLine: _operations.MoveToEndOfLine(select); break;
                    case EditCommand.MoveToStartOfDocument: _operations.MoveToStartOfDocument(select); break;
                    case EditCommand.MoveToEndOfDocument: _operations.MoveToEndOfDocument(select); break;
                    case EditCommand.ScrollUp: _operations.ScrollUpAndMoveCaretIfNecessary(); break;
                    case EditCommand.ScrollDown: _operations.ScrollDownAndMoveCaretIfNecessary(); break;
                    case EditCommand.DeleteCharacterLeft: _operations.DeleteCharacterToLeft(null); break;
                    case EditCommand.DeleteCharacterRight: _operations.DeleteCharacterToRight(null); break;
                    case EditCommand.DeleteWordLeft: _operations.DeleteWordToLeft(null); break;
                    case EditCommand.DeleteWordRight: _operations.DeleteWordToRight(null); break;
                    case EditCommand.InsertNewLine: _operations.InsertNewline(null); break;
                    case EditCommand.InsertTab: _operations.InsertTab(null); break;
                    //case EditCommand.IndentSelection: _operations.IndentSelection(null); break;
                    //case EditCommand.UnindentSelection: _operations.UnindentSelection(null); break;
                    case EditCommand.SelectAll: _operations.SelectAll(); break;
                    case EditCommand.SelectWord: _operations.SelectCurrentWord(); break;
                    case EditCommand.SelectLine: _operations.SelectCurrentLine(); break;
                    case EditCommand.DeleteBack: Delete(false); break;
                    case EditCommand.DeleteForward: Delete(true); break;
                    case EditCommand.Undo: Undo(); break;
                    case EditCommand.Redo: Redo(); break;
                    case EditCommand.Cut: Cut(); break;
                    case EditCommand.Copy: Copy(); break;
                    case EditCommand.Paste: Paste(); break;
                    case EditCommand.MacroPlay: MacroPlay(); macroIgnore = true; break;
                    case EditCommand.MacroRecordToggle: MacroRecordToggle(); macroIgnore = true; break;
                }
            }
            finally {
                _inCommand = old;
            }
            if (!macroIgnore && _recording && !_inCommand)
                _currentMacro.Add(new MacroCommand(command));
        }

        private void PerformLayout(double height, bool deferParenting) {
            if (_lineVisualFactory == null) 
                _lineVisualFactory = new TextLineVisualFactory(_textBuffer, _classifier, _formatMap, _defaultStyle);

            int currentLine = _textBuffer.GetLineContaining(_startOfFirstVisibleLine);
            double currentTopOffset = _firstVisibleLineOffset;
            var addedLines = new Dictionary<TextPoint,TextPoint>();
            TextPoint startPoint = _startOfFirstVisibleLine;
            TextPoint usablePoint = startPoint;
            List<TextBlock> needsParenting = null;

            // Walk down, laying out lines
            if (_textLayer != null)
                while (currentLine < _textBuffer.LineCount) {
                    var visualInfo = GetCachedTextLineVisualIndexForLine(currentLine);
                    addedLines.Add(visualInfo.Point, visualInfo.Point);
                    startPoint = visualInfo.Point;
                    var lineVisual = visualInfo.Visual;

                    Canvas.SetTop(lineVisual, currentTopOffset);

                    if (lineVisual.Parent == null)
                        if (deferParenting) {
                            if (needsParenting == null)
                                needsParenting = new List<TextBlock>();
                            needsParenting.Add(lineVisual);
                        }
                        else
                            _textLayer.Children.Add(lineVisual);

                    currentTopOffset += lineVisual.ActualHeight;

                    if (currentTopOffset > height)
                        break;

                    usablePoint = startPoint;

                    currentLine++;
                }

            if (needsParenting != null)
                Dispatcher.BeginInvoke(delegate {
                    foreach (var visual in needsParenting)
                        if (visual.Tag == null && visual.Parent == null)
                            _textLayer.Children.Add(visual);
                });

            _startOfLastVisibleLine = startPoint;
            _startOfLastUsableLine = usablePoint;

            var pageSize = usablePoint.Line - _startOfFirstVisibleLine.Line;
            if (pageSize < 1) pageSize = 1;
            if (currentLine < _textBuffer.LineCount || pageSize > _pageSize)
                _pageSize = pageSize;

            TrimLines(addedLines);

            UpdateCaret();
            UpdateSelection();
            UpdateScrollBars();

            if (ViewChanged != null)
                ViewChanged(this, EventArgs.Empty);

            // Ensure the input box is in front.
            if (_inputTextBox != null && _textLayer != null) {
                if (deferParenting) {
                    Dispatcher.BeginInvoke(delegate {
                        var parent = _inputTextBox.Parent as Panel;
                        if (parent != null) {
                            parent.Children.Remove(_inputTextBox);
                            parent.Children.Add(_inputTextBox);
                            _inputTextBox.IsTabStop = true;
                        }
                    });
                }
                else {
                    var parent = _inputTextBox.Parent as Panel;
                    if (parent != null) {
                        parent.Children.Remove(_inputTextBox);
                        parent.Children.Add(_inputTextBox);
                        _inputTextBox.IsTabStop = true;
                    }
                }
            }
        }

        private void InsertText(string text) {
            if (_selectionSpan == null || _selectionSpan.Length == 0)
                _textBuffer.Insert(CaretPosition, text);
            else {
                var span = _selectionSpan;
                _selectionSpan = null;
                _textBuffer.Replace(span.Start, span.Length, text);
            }
            if (_recording && !_inCommand)
                _currentMacro.Add(new MacroInsertText(text));
        }

        private void Delete(bool forward) {
            if (_selectionSpan == null || _selectionSpan.Length == 0) 
                if (forward)
                    _operations.DeleteCharacterToRight(null);
                else
                    _operations.DeleteCharacterToLeft(null);
            else {
                var span = _selectionSpan;
                _selectionSpan = null;
                _textBuffer.Delete(span.Start, span.Length);
            }
        }

        private void Undo() {
            if (_undoStack.Count > 0) {
                _inUndo = true;
                try {
                    var item = _undoStack.Pop();
                    var redoItem = item.Apply();
                    _redoStack.Push(redoItem);
                    MarkNoUndoMerge();
                }
                finally {
                    _inUndo = false;
                }
            }
        }

        private void Redo() {
            if (_redoStack.Count > 0) {
                _inUndo = true;
                try {
                    var item = _redoStack.Pop();
                    var undoItem = item.Apply();
                    _undoStack.Push(undoItem);
                }
                finally {
                    _inUndo = false;
                }
            }
        }

        private void Cut() {
            if (_selectionSpan != null && _selectionSpan.Length > 0) {
                Copy();
                Delete(true);
            }
        }

        private void Copy() {
            if (_selectionSpan != null && _selectionSpan.Length > 0) {
                var text = _textBuffer.GetText(_selectionSpan.Start, _selectionSpan.Length);
                Clipboard.Content = text;
            }
        }

        private void Paste() {
            if (Clipboard.HasContent) {
                MarkNoUndoMerge();
                InsertText(Clipboard.Content);
                EnsureCaretVisible();
            }
        }

        private void RecordUndo(UndoItem item) {
            if (_undoStack.Count > 0) {
                var top = _undoStack.Peek();
                if (item.CanMergeWith(top)) {
                    _undoStack.Pop();
                    item = item.MergeWith(top);
                }
            }
           _undoStack.Push(item);
           _redoStack.Clear();
        }

        private void StartUndoGroup() {
            _groupStack.Push(_undoStack);
            _undoStack = new Stack<UndoItem>();
            _redoStack.Clear();
        }

        private void FinishUndoGroup() {
            Debug.Assert(_groupStack.Count > 0);
            var last = _groupStack.Pop();
            var newGroup = new UndoGroup(_undoStack);
            _undoStack = last;
            RecordUndo(newGroup);
        }

        private void MacroPlay() {
            if (!_recording) {
                StartUndoGroup();
                try {
                    foreach (var item in _currentMacro)
                        item.Play(this);
                }
                finally {
                    FinishUndoGroup();
                }
            }
        }

        private void MacroRecordToggle() {
            if (_recording)
                _recording = false;
            else {
                MarkNoUndoMerge();
                _currentMacro.Clear();
                _recording = true;
            }
        }

        private void MarkNoUndoMerge() {
            if (_undoStack.Count > 0) 
                _undoStack.Push(new UndoNoMergeMarker(_undoStack.Pop()));
        }

        private LineInfo GetCachedTextLineVisualIndexForLine(int lineNumber) {
            BufferLine bufferLine = _textBuffer.GetTextLine(lineNumber);
            TextPoint startPoint = bufferLine.StartPoint;
            TextSpan span = bufferLine.TextSpan;

            var index = _lineVisuals.IndexOf(l => l.Point, startPoint);

            if (index < 0) {
                var visual = _lineVisualFactory.CreateTextLineVisual(lineNumber, -1);
                var result = new LineInfo() { Point = startPoint, Visual = visual, Span = span };
                _lineVisuals.Add(result);
                return result;
            }
            else
                return _lineVisuals[index];
        }

        private class LineComparer : IComparer<LineInfo> {

            public int Compare(LineInfo x, LineInfo y) {
                return x.Span.Start - y.Span.Start;
            }

            public static LineComparer Instance = new LineComparer();
        }

        private void TrimLines(Dictionary<TextPoint, TextPoint> addedLines) {
            var removeLines = (from info in _lineVisuals
                               where !addedLines.ContainsKey(info.Point)
                               select info).ToArray();
            foreach (var info in removeLines) {
                int index = _lineVisuals.IndexOf(l => l.Point, info.Point);
                if (index >= 0) 
                    _lineVisuals.RemoveAt(index);
                // Tag the visual as being removed.
                info.Visual.Tag = "removed";
                _textLayer.Children.Remove(info.Visual);
            }
        }

        private void UpdateCaret() {
            if (_caret != null) {
                int caretPosition = CaretPosition;

                LineInfo lineInfo = GetLineInfoForPosition(caretPosition, false);
                if (lineInfo.Visual != null) {
                    double actualDistance = CharacterPosition(lineInfo, caretPosition);
                    _caret.X1 = _caret.X2 = actualDistance;
                    _caret.Y1 = Canvas.GetTop(lineInfo.Visual);
                    _caret.Y2 = _caret.Y1 + lineInfo.Visual.ActualHeight;
                    _caret.Visibility = Visibility.Visible;
                }
                else
                    _caret.Visibility = Visibility.Collapsed;
            }
        }

        private void UpdateSelection() {
            if (_selectionPath != null) {
                if (SelectionSpan == null || SelectionSpan.Length == 0) {
                    _selectionPath.Data = null;
                    UpdateInputBoxToSelection();
                    return;
                }

                int lastVisibleCharacter = _textBuffer.GetEndOfLineContaining(_startOfLastVisibleLine);
                if (SelectionSpan.Start > lastVisibleCharacter || SelectionSpan.End < _startOfFirstVisibleLine) {
                    _selectionPath.Data = null;
                    UpdateInputBoxToSelection();
                    return;
                }

                int startPoint = SelectionSpan.Start;
                if (startPoint < _startOfFirstVisibleLine)
                    startPoint = _startOfFirstVisibleLine;

                int endPoint = SelectionSpan.End;
                if (endPoint > lastVisibleCharacter)
                    endPoint = lastVisibleCharacter;

                _selectionPath.Data = GetTextSpanGeometry(startPoint, endPoint);
            }
            UpdateInputBoxToSelection();
        }

        private void UpdateScrollBars() {
            if (_veriticalScrollBar != null) 
                _veriticalScrollBar.Value = FirstVisibleLine;
        }

        private LineInfo GetLineInfoForPosition(int position, bool force) {
            if (position >= _textBuffer.Length)
                position = _textBuffer.Length - 1;
            foreach (var info in _lineVisuals) {
                if (position >= info.Span.Start && position < info.Span.End)
                    return info;
            }
            if (force)
                return GetCachedTextLineVisualIndexForLine(_textBuffer.GetLineContaining(position));
            return LineInfo.Empty;
        }

        Geometry GetTextSpanGeometry(int startPosition, int endPosition) {
            
            if (startPosition > endPosition) {
                int temp = startPosition;
                startPosition = endPosition;
                endPosition = temp;
            }

            GeometryGroup gg = new GeometryGroup();

            double x, y, width, height;
            var startInfo = GetLineInfoForPosition(startPosition, false);
            var endInfo = GetLineInfoForPosition(endPosition, false);

            x = CharacterPosition(startInfo, startPosition);
            y = Canvas.GetTop(startInfo.Visual);
            if (startInfo.Point == endInfo.Point) {
                width = CharacterPosition(startInfo, endPosition) - x;
                height = startInfo.Visual.ActualHeight;
                Rect r = new Rect(x, y, width, height);
                gg.Children.Add(new RectangleGeometry() { Rect=r, RadiusX=2, RadiusY=2 });
            }
            else {
                width = startInfo.Visual.ActualWidth - x;
                height = startInfo.Visual.ActualHeight;
                Rect r = new Rect(x, y, width, height);
                gg.Children.Add(new RectangleGeometry() { Rect=r, RadiusX=2, RadiusY=2 });

                var lines = (from lineInfo in _lineVisuals
                            where lineInfo.Span.End >= startPosition && lineInfo.Span.Start <= endPosition 
                            orderby lineInfo.Span.Start
                            select lineInfo).ToArray();

                for (int i = 1; i < lines.Length - 1; i++) {
                    var midLine = lines[i];
                    r = new Rect(-2, Canvas.GetTop(midLine.Visual), midLine.Visual.ActualWidth + 4, midLine.Visual.ActualHeight);
                    gg.Children.Add(new RectangleGeometry() { Rect = r, RadiusX = 2, RadiusY = 2 });
                }

                var endLine = lines[lines.Length - 1];
                r = new Rect(-2, Canvas.GetTop(endLine.Visual), CharacterPosition(endLine, endPosition) + 4, endLine.Visual.ActualHeight);
                    
                gg.Children.Add(new RectangleGeometry() { Rect=r, RadiusX=2, RadiusY=2 });
            }

            gg.FillRule = FillRule.Nonzero;
            //return gg.GetOutlinedPathGeometry(20, ToleranceType.Absolute);
            return gg;
        }

        private void InitializeTextBuffer() {
            _startOfFirstVisibleLine = new TextPoint(_textBuffer, 0, TrackingMode.Negative);
            _startOfLastVisibleLine = new TextPoint(_textBuffer, 0);
            _caretPosition = new TextPoint(_textBuffer, 0);
            _textBuffer.Changed += OnTextBufferChanged;
            _undoStack.Clear();
            _redoStack.Clear();
            //if (_veriticalScrollBar != null)
            //    _veriticalScrollBar.Maximum = _textBuffer.LineCount - 1;
        }

        private void DeinitializeTextBuffer() {
            if (_textBuffer != null) {
                _textBuffer.Changed -= OnTextBufferChanged;
                _undoStack.Clear();
                _redoStack.Clear();
                _lineVisualFactory = null;
            }
        }

        private void OnTextBufferChanged(object sender, Microsoft.Windows.Controls.Specialized.TextModel.TextChangedEventArgs e) {
            TextChange change = e.PriorVersion.Change;
            InvalidateLines(change.Position, change.NewEnd);
            if (_veriticalScrollBar != null)
                _veriticalScrollBar.Maximum = _textBuffer.LineCount - 1;
            if (!_inUndo) 
                RecordUndo(new UndoChange(_textBuffer, e.PriorVersion.Change));
        }

        private void OnLayoutChanged(object sender, EventArgs args) {
            if (_veriticalScrollBar != null) {
                if (_veriticalScrollBar.ViewportSize != PageSize)
                    _veriticalScrollBar.ViewportSize = PageSize;
                _veriticalScrollBar.SmallChange = 1;
                _veriticalScrollBar.LargeChange = PageSize;
            }
        }

        private void OnClassificationChanged(object sender, ClassificationChangedEventArgs change) {
            InvalidateLines(change.ChangeSpan.Start, change.ChangeSpan.End);
        }

        private void InvalidateLines(int start, int end) {
            int startAffectedLineNumber = _textBuffer.GetLineContaining(start);
            int endAffectedLineNumber = _textBuffer.GetLineContaining(end);
            if (startAffectedLineNumber <= this.LastVisibleLine &&
                endAffectedLineNumber >= this.FirstVisibleLine) {

                // Remove all the lines that contain this range.
                for (int lineIndex = _lineVisuals.Count - 1; lineIndex >= 0; lineIndex--) {
                    var line = _lineVisuals[lineIndex];
                    if (line.Span.Start <= end && line.Span.End >= start) {
                        _textLayer.Children.Remove(line.Visual);
                        _lineVisuals.RemoveAt(lineIndex);
                    }
                }

                DisplayLine(FirstVisibleLine, _firstVisibleLineOffset);
            }
        }

        public void InvalidateView() {
            _lineVisuals.Clear();
            if (_textLayer != null)
                _textLayer.Children.Clear();
            InvalidateArrange();
        }

        private int CharacterOffsetOf(LineInfo info, double pos) {
            var text = TextLineVisualFactory.SpanText(info.Span);
            var len = text.Length;
            for (int i = 1; i < len; i++) {
                var charPos = _lineVisualFactory.CreateTextLineVisual(info.Span, i).ActualWidth;            
                if (charPos > pos)
                    return info.Span.Start + i - 1;
            }
            return info.Span.Start + text.Length ;
        }

        private double CharacterPosition(LineInfo info, int position) {
            var offset = position - info.Span.Start;
            var tempBlock = _lineVisualFactory.CreateTextLineVisual(info.Span, offset);            
            return tempBlock.ActualWidth;
        }

        private string Subtext(string value, int length) {
            if (length == 0 || value == null)
                return string.Empty;
            if (length >= value.Length)
                return value;
            return value.Substring(0, length);
        }

        private abstract class UndoItem {
            public abstract UndoItem Apply();
            public abstract bool CanMergeWith(UndoItem item);
            public abstract UndoItem MergeWith(UndoItem item);
        }

        private  class UndoNoMergeMarker : UndoItem {
            private UndoItem _item;

            public UndoNoMergeMarker(UndoItem item) {
                _item = item;
            }

            public override UndoItem Apply() {
                return new UndoNoMergeMarker(_item.Apply());
            }

            public override bool CanMergeWith(UndoItem item) {
                return false;
            }

            public override UndoItem MergeWith(UndoItem item) {
                throw new NotImplementedException();
            }
        }

        private class UndoChange : UndoItem {
            private ITextBuffer _buffer;
            private TextChange _change;
            private bool _forward;

            public UndoChange(ITextBuffer buffer, TextChange change) : this(buffer, change, false) { }
            private UndoChange(ITextBuffer buffer, TextChange change, bool forward) {
                Debug.Assert(buffer != null && change != null);
                _buffer = buffer;
                _change = change;
                _forward = forward;
            }

            public override UndoItem Apply() {
                var change = _buffer.CreateEdit();
                if (_forward) {
                    change.Delete(_change.Position, _change.OldText.Length);
                    change.Insert(_change.Position, _change.NewText);
                }
                else {
                    change.Delete(_change.Position, _change.NewText.Length);
                    change.Insert(_change.Position, _change.OldText);
                }
                change.Apply();
                return new UndoChange(_buffer, _change, !_forward);
            }

            public override bool CanMergeWith(UndoItem item) {
                if (string.IsNullOrEmpty(_change.OldText) && !string.IsNullOrEmpty(_change.NewText)) {
                    var other = item as UndoChange;
                    if (other != null) {
                        if (!string.IsNullOrEmpty(other._change.NewText) && string.IsNullOrEmpty(other._change.OldText) &&
                            _change.Position == other._change.Position + other._change.NewText.Length) {
                            // This is an insert adjacent to another insert. We can merge them.
                            return true;
                        }
                    }
                }
                return false;
            }

            public override UndoItem MergeWith(UndoItem item) {
                var other = item as UndoChange;
                if (other == null)
                    throw new ArgumentException();
                Debug.Assert(string.IsNullOrEmpty(_change.OldText) && !string.IsNullOrEmpty(_change.NewText));
                Debug.Assert(string.IsNullOrEmpty(other._change.OldText) && !string.IsNullOrEmpty(other._change.NewText));
                return new UndoChange(_buffer, new TextChange(other._change.Position, other._change.OldText,
                    other._change.NewText + _change.NewText));
            }
        }

        private class UndoMove : UndoItem {
            private CodeEditor _editor;
            private int _oldCaret;

            public UndoMove(CodeEditor editor, int oldCaret) {
                Debug.Assert(editor != null);
                _editor = editor;
                _oldCaret = oldCaret;
            }

            public override UndoItem Apply() {
                int currentCaret = _editor.CaretPosition;
                _editor.CaretPosition = _oldCaret;
                _editor.EnsureCaretVisible();
                return new UndoMove(_editor, currentCaret);
            }

            public override bool CanMergeWith(UndoItem item) {
                return item is UndoMove;
            }

            public override UndoItem MergeWith(UndoItem item) {
                return item;
            }
        }

        private class UndoGroup : UndoItem {
            private Stack<UndoItem> _group;

            public UndoGroup(Stack<UndoItem> group) {
                _group = group;
            }

            public override UndoItem Apply() {
                var redoStack = new Stack<UndoItem>();
                while (_group.Count > 0) {
                    var top = _group.Pop();
                    redoStack.Push(top.Apply());
                }
                return new UndoGroup(redoStack);
            }

            public override bool CanMergeWith(UndoItem item) {
                return false;
            }

            public override UndoItem MergeWith(UndoItem item) {
                throw new NotImplementedException();
            }
        }

        private abstract class MacroItem {
            public abstract void Play(CodeEditor editor);
        }

        private class MacroCommand: MacroItem {
            private EditCommand _command;

            public MacroCommand(EditCommand command) {
                _command = command;
            }

            public override void Play(CodeEditor editor) {
                editor.PerformCommand(_command);
            }
        }

        private class MacroInsertText : MacroItem {
            private string _text;

            public MacroInsertText(string text) {
                _text = text;
            }

            public override void Play(CodeEditor editor) {
                editor.InsertText(_text);
            }
        }

        private class MouseWheelHook {
            public static MouseWheelHook Instance; 

            public MouseWheelHook() {
                if (HtmlPage.IsEnabled) {
                    HtmlPage.Window.AttachEvent("DOMMouseScroll", this.HandleMouseWheel);
                    HtmlPage.Window.AttachEvent("onmousewheel", this.HandleMouseWheel);
                    HtmlPage.Document.AttachEvent("onmousewheel", this.HandleMouseWheel);
                }
            }

            private void HandleMouseWheel(object sender, HtmlEventArgs args) {
                double delta = 0;

                ScriptObject eventObj = args.EventObject;

                if (eventObj.GetProperty("wheelDelta") != null) {
                    delta = ((double)eventObj.GetProperty("wheelDelta")) / 120;


                    if (HtmlPage.Window.GetProperty("opera") != null)
                        delta = -delta;
                }
                else if (eventObj.GetProperty("detail") != null) {
                    delta = -((double)eventObj.GetProperty("detail")) / 3;

                    if (HtmlPage.BrowserInformation.UserAgent.IndexOf("Macintosh") != -1)
                        delta = delta * 3;
                }

                if (delta != 0) {
                    if (this.OnMouseWheel(delta, args))
                        args.PreventDefault();
                }
            }

            private bool OnMouseWheel(double delta, HtmlEventArgs e) {
                Point mousePosition = new Point(e.ClientX, e.ClientY);
                if (HtmlPage.BrowserInformation.Name.Contains("Netscape"))
                    mousePosition = new Point(e.ScreenX, e.ScreenY);

                UIElement rootVisual = (UIElement)Application.Current.RootVisual;

                if (rootVisual != null) {
                    //var editor = rootVisual.HitTest(mousePosition).FirstOrDefault(element => element is CodeEditor) as CodeEditor;
                    var editor = VisualTreeHelper.FindElementsInHostCoordinates(mousePosition, rootVisual).FirstOrDefault(element => element is CodeEditor) as CodeEditor;
                    if (editor != null) {
                        editor.DoMouseWheel(delta);
                        return true;
                    }
                }
                return false;
            }


            internal static void Hook() {
                Instance = new MouseWheelHook();
            }
        }
    }

    static class Makeups {
        public static int IndexOf<T, K>(this IList<T> list, Func<T, K> mapper, K value) where K: IEquatable<K> {
            var len = list.Count;
            for (int i = 0; i < len; i++)
                if (mapper(list[i]).Equals(value))
                    return i;
            return -1;
        }
    }

}